'use strict';
import sequelize from 'sequelize';
import bcrypt from 'bcrypt-nodejs';
import jwt from 'jsonwebtoken';
import gravatar from 'gravatar';
import { IUserAttr, IUserInst } from '../model/user';
import BaseService from './base';
import { loginRule, registRule, updateRule } from '../rules/user';

const SALT_WORK_FACTOR = 5;

interface ILoginParams {
  username: string;
  password: string;
}
interface IRegisterParams {
  username: string;
  password: string;
  email?: string;
  avatar?: string;
}

interface IUserUpdateParams {
  username?: string;
  avatar?: string;
  email?: string;
  gender?: number;
  signature?: string;
  website?: string;
}

export default class extends BaseService {
  /**
   * @description: 根据用户名获取用户
   * @param {string} username
   * @return: Promise
   */
  async getByName(username: string): Promise<IUserAttr> {
    const { ctx } = this;
    return await ctx.model.User.findOne({
      where: {
        username,
      },
    });
  }

  /**
   * @description: 登录
   * @param {string} username
   * @param {string} password
   * @return: Promise
   */
  async doLogin({ username, password }: ILoginParams){
    const { app, ctx } = this;
    ctx.validate(loginRule, { username, password });
    const user = await this.getByName(username);
    if (!user) {
      ctx.fail('用户不存在', 404001)
    }
    const isMatch: boolean = await bcrypt.compareSync(password, user.password);
    if(!isMatch) ctx.fail('密码错误', 422001)
    const token = jwt.sign(JSON.parse(JSON.stringify({
      id: user.id,
      username: user.username,
      email: user.email,
    })), app.config.jwtSecret, { expiresIn: '30d' });
    return {
      token,
      userInfo: {
        id: user.id,
        username: user.username,
        avatar: user.avatar,
      },
    };
  }

  /**
   * @description: 注册
   * @param {number} username 名称
   * @param {number} password 密码
   * @param {number} email 邮箱
   * @param {number} avatar 头像
   * @return: Promise<IUserInst>
   */
  async create({username, password, email, avatar}: IRegisterParams): Promise<IUserInst> {
    const { ctx } = this;
    const payload = {username, password, email, avatar};
    ctx.validate(registRule, payload);
    const user = await this.getByName(payload.username);
    if (user) ctx.fail('用户名已被创建', 409001);
    const salt = bcrypt.genSaltSync(SALT_WORK_FACTOR);
    const hash = bcrypt.hashSync(payload.password, salt);
    payload.password = hash;
    if (!payload.avatar) {
      const avatar = gravatar.url(payload.username, { s: '100', r: 'x', d: 'retro' }, true);
      payload.avatar = avatar;
    }
    return await ctx.model.User.create(payload);
  }

  /**
   * @description: 关注用户
   * @param {number} followerId 来自谁的关注
   * @param {number} userId 被关注的用户
   * @return: Promise
   */
  async toggleFollow(followerId: number, userId: number): Promise<boolean> {
    const { ctx } = this;
    let isFollow: boolean;
    let condition: object;
    const query = {
      followerId,
      userId,
    };
    const hasFollower = await ctx.model.Follower.scope('all').findOne({
      where: query,
    });
    if (hasFollower) {
      if (hasFollower.status === 1) {
        condition = { status: 0 };
        isFollow = false;
      } else {
        condition = { status: 1 };
        isFollow = true;
      }
      await ctx.model.Follower.scope('all').update(condition, {
        where: query,
      });
    } else {
      await ctx.model.Follower.create({
        status: 1,
        userId,
        followerId,
      });
      isFollow = true;
    }
    return isFollow;
  }

  // 获取用户基本信息
  async getBasicInfo(userId: number): Promise<IUserInst>{
    const { ctx } = this;
    const user: IUserInst = await this.ctx.model.User.findByPk(userId, {
      attributes: { exclude: ['password'] },
    });
    if (!user) ctx.fail('用户不存在', 404001)
    return user;
  }

  /**
   * @description: 批量获取用户详细信息
   * @param {number} userId 用户ID
   * @return: Promise
   */
  async getUserInfo(userId: number) {
    const { ctx } = this;
    const userBasicInfo = await this.getBasicInfo(userId);
    const userInfo = userBasicInfo.toJSON();
    const userArticleCounts = await ctx.model.Article.findAll({
      where: {
        authorId: userId,
      },
      attributes: [
        'authorId',
        [sequelize.fn('SUM', sequelize.col('pvCount')), 'pvCount'],
        [sequelize.fn('SUM', sequelize.col('likeCount')), 'likeCount'],
        [sequelize.fn('SUM', sequelize.col('collectionCount')), 'collectionCount'],
        // [sequelize.fn('COUNT', sequelize.col('*')), 'articleCount'],
      ],
    });
    const commentCount = await ctx.model.Comment.count({
      where: {
        fromUserId: userId,
      },
    });
    const articleCount = await ctx.model.Article.count({
      where: {
        authorId: userId,
      },
    });
    const followerCount = await ctx.model.Follower.count({
      where: {
        userId,
      },
    });
    const followingCount = await ctx.model.Follower.count({
      where: {
        followerId: userId,
      },
    });

    const { pvCount, likeCount, collectionCount } = userArticleCounts[0];

    return {
      ...userInfo,
      pvCount: Number(pvCount),
      likeCount: Number(likeCount),
      collectionCount: Number(collectionCount),
      articleCount,
      followerCount,
      followingCount,
      commentCount
    };
  }

  /**
   * @description: 更新信息
   * @param {number} userId 用户ID
   * @param {IUserUpdateParams} payload
   * @return: Promise
   */
  async doUpdate(userId: number, payload: IUserUpdateParams) {
    const { ctx } = this;
    ctx.validate(updateRule, payload);
    const user = await this.ctx.model.User.findByPk(userId);
    if (!user) ctx.fail('用户不存在', 404001);
    return await ctx.model.User.update(payload, {
      where: {
        id: userId,
      },
    });
  }
}
